﻿using System;
using System.Collections.Generic;
using System.Linq;
using log4net;
using NHibernate;
using NhJump.Persistence.Context;

namespace NhJump.Persistence
{
    public class UnitOfWorkFactory
    {
        private readonly ISessionFactory _sessionFactory;
        private static readonly ILog Log = LogManager.GetLogger(typeof(UnitOfWorkFactory));

        private readonly Guid _unitOfWorkFactoryId;
        public UnitOfWorkFactory(INHibernateConfigProvider configProvider)
        {
            _unitOfWorkFactoryId = Guid.NewGuid();
            Log.DebugFormat("In constructor id={0}", _unitOfWorkFactoryId );
            _sessionFactory = configProvider.GetConfiguration().BuildSessionFactory();
        }

        public UnitOfWork BuildUnitOfWork()
        {
            Log.DebugFormat("In BuildUnitOfWork, UnitofWorkFactoryId={0}", _unitOfWorkFactoryId);
            return new UnitOfWork(_sessionFactory);
        }

        public UnitOfWork BuildAutoBoundUnitOfWork()
        {
            Log.DebugFormat("In BuildAutoBoundUnitOfWork, UnitofWorkFactoryId={0}", _unitOfWorkFactoryId);
            return new AutoBoundUnitOfWork(_sessionFactory);           
        }

#region Singleton

        private static string _currentFactoryKey;
        public static string CurrentFactoryKey
        {
            get { return _currentFactoryKey ?? string.Empty; }
            set { _currentFactoryKey = value; }
        }

        public static UnitOfWorkFactory ForServiceType(Type serviceType)
        {
            string key = serviceType.ToString();
            Log.DebugFormat("ForServiceTypeOf={0}", key);
            return _factoryRegistry[key];
        }

        public static UnitOfWorkFactory Current
        {
            get
            {
                Log.DebugFormat("CurrentFactoryKey={0}", CurrentFactoryKey);

                // We need to rethink this in the light of having multiple unit of work factories....
                // Wcf uses the ForServiceType method which is fine but web apps will use this method.
                // We therefore need to ensure that we don't use multiple factories in a web app.
                if (_factoryRegistry.Keys.Count>1)
                    throw new Exception("Using the UnitOfWorkFactory.Current is invalid when there are multiple unit of work factories.");

                string key = _factoryRegistry.Keys.First();
                return _factoryRegistry[key];
            }
        }

        private static Dictionary<string, UnitOfWorkFactory> _factoryRegistry;

        public static void Register(INHibernateConfigProvider configProvider)
        {
            // Supports the case where there is only one factory.
            Register(string.Empty, configProvider);
        }

        public static UnitOfWorkFactory Register(Type serviceType, INHibernateConfigProvider configProvider)
        {
            return Register(serviceType.ToString(), configProvider);
        }

        private static UnitOfWorkFactory Register(string key, INHibernateConfigProvider configProvider)
        {
            _factoryRegistry = _factoryRegistry ?? new Dictionary<string, UnitOfWorkFactory>();
            var unitOfWorkFactory = new UnitOfWorkFactory(configProvider);
            _factoryRegistry.Add(key, unitOfWorkFactory);
            return unitOfWorkFactory;
        }

        public static void Register(Type serviceType, UnitOfWorkFactory factory)
        {
            _factoryRegistry = _factoryRegistry ?? new Dictionary<string, UnitOfWorkFactory>();
            var key = serviceType.ToString();
            if (_factoryRegistry.ContainsKey(key)) return;
            _factoryRegistry.Add(key, factory);
        }

        public static void Unregister(Type serviceType)
        {
            var key = serviceType.ToString();
            if (_factoryRegistry.ContainsKey(key))
            {
                _factoryRegistry.Remove(key);
            }
        }

#endregion
    }
}
